/*
 * Copyright (c) 2022 Winner Microelectronics Co., Ltd. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/****************************************************************************
 * @file     wm_i2c.c
 * @author
 * @version
 * @date
 * @brief
 *
 * Copyright (c) 2014 Winner Microelectronics Co., Ltd. All rights reserved.
 *****************************************************************************/

#include "wm_i2c.h"
#include "stddef.h"

#define I2C_FREQ_MAX            (400000)
#define I2C_FREQ_MIN            (100000)
#define I2C_WRITE                (0x80)
#define I2C_READ                (0x00)
typedef struct {
    uint8_t addr;
    uint8_t dev_addr;
    uint8_t state;
    uint8_t *buf;
    uint16_t len;
    uint16_t cnt;
    uint8_t cmd;
    void (*transfer_done)(void);
} i2c_desc;
enum {
    START,
    RESTART,
    TRANSMIT,
    PRERECEIVE,
    RECEIVE,
    STOP,
    DONE,
    IDLE,
};
static i2c_desc i2c_transfer;

ATTRIBUTE_ISR void i2c_I2C_IRQHandler(void)
{
    int i2c_sr;
    csi_kernel_intrpt_enter();
    i2c_sr = I2C->CR_SR;
    I2C->CR_SR = 1;
    if (i2c_sr & 0x20) {
        printf("I2C AL lost\r\n");
    }
    if (i2c_sr & 0x01) {
        if ((i2c_sr & 0x80) == 0) {
            switch (i2c_transfer.state) {
                case START:
                    I2C->TX_RX = i2c_transfer.addr;
                    I2C->CR_SR = I2C_CR_WR;
                    if ((i2c_transfer.cmd & I2C_WRITE) == I2C_WRITE) {
                        i2c_transfer.state = TRANSMIT;
                    } else {
                        i2c_transfer.state = RESTART;
                    }
                    break;

                case RESTART:
                    I2C->TX_RX = (i2c_transfer.dev_addr | 0x01);
                    I2C->CR_SR = (I2C_CR_STA | I2C_CR_WR);
                    i2c_transfer.state = PRERECEIVE;
                    break;

                case TRANSMIT:
                    I2C->TX_RX = i2c_transfer.buf[i2c_transfer.cnt++];
                    I2C->CR_SR = I2C_CR_WR;
                    if (i2c_transfer.cnt == i2c_transfer.len) {
                        i2c_transfer.state = STOP;
                    }
                    break;

                case PRERECEIVE:
                    i2c_transfer.state = RECEIVE;
                    I2C->CR_SR = I2C_CR_RD;
                    break;
                case RECEIVE:
                    i2c_transfer.buf[i2c_transfer.cnt++] = I2C->TX_RX;
                    if (i2c_transfer.cnt == (i2c_transfer.len - 1)) {
                        I2C->CR_SR = (I2C_CR_STO | I2C_CR_NAK | I2C_CR_RD);
                        i2c_transfer.state = STOP;
                    } else if (i2c_transfer.len == 1) {
                        I2C->CR_SR = (I2C_CR_STO | I2C_CR_NAK | I2C_CR_RD);
                        i2c_transfer.state = DONE;
                        if (i2c_transfer.transfer_done) {
                            i2c_transfer.transfer_done();
                        }
                    } else {
                        I2C->CR_SR = I2C_CR_RD;
                    }
                    break;

                case STOP:
                    I2C->CR_SR = I2C_CR_STO;
                    i2c_transfer.state = DONE;
                    if (i2c_transfer.transfer_done) {
                        i2c_transfer.transfer_done();
                    }
                    break;
            }
        } else {
            if ((i2c_transfer.state == STOP) && i2c_transfer.cmd != I2C_WRITE) {
                i2c_transfer.buf[i2c_transfer.cnt] = I2C->TX_RX;
                i2c_transfer.state = DONE;
                if (i2c_transfer.transfer_done) {
                    i2c_transfer.transfer_done();
                }
            }
        }
    }
    csi_kernel_intrpt_exit();
}

void tls_i2c_init(u32 freq)
{
    u32 div = 0;
    tls_sys_clk clk;

    if (freq < I2C_FREQ_MIN) {
        freq = I2C_FREQ_MIN;
    } else if (freq > I2C_FREQ_MAX) {
        freq = I2C_FREQ_MAX;
    }
    tls_sys_clk_get(&clk);

    div = (clk.apbclk * 1000000)/(5 * freq) - 1;
    tls_reg_write32(HR_I2C_PRER_LO, div & 0xff);
    tls_reg_write32(HR_I2C_PRER_HI, (div>>8) & 0xff);

    /** enable I2C | Disable Int*/
    tls_reg_write32(HR_I2C_CTRL, I2C_CTRL_INT_DISABLE | I2C_CTRL_ENABLE);
    tls_irq_enable(I2C_IRQn);
}

/**
 * @brief    send stop signal
 *
 */
void tls_i2c_stop(void)
{
    tls_reg_write32(HR_I2C_CR_SR, I2C_CR_STO);
    while (tls_reg_read32(HR_I2C_CR_SR) & I2C_SR_TIP);
}

/**
 * @brief    waiting for ack signal
 * @retval
 *    - \ref WM_FAILED
 *    - \ref WM_SUCCESS
 */
int tls_i2c_wait_ack(void)
{
    u16 errtime = 0;
    u32 value;

    while (tls_reg_read32(HR_I2C_CR_SR) & I2C_SR_TIP);
    value = tls_reg_read32(HR_I2C_CR_SR);
    while (value & I2C_SR_NAK) {
        errtime ++;
        if (errtime > 512) {
            printf("wait ack err\n");
            tls_i2c_stop();
            return WM_FAILED;
        }
        value = tls_reg_read32(HR_I2C_CR_SR);
    }

    return WM_SUCCESS;
}

/**
 * @brief    writes the data to data register of I2C module
 * when \ifstart one the start signal will be sent followed by the \data
 * when \ifstart zero only the \data will be send
 * @param[in] data    the data will be write to the data register of I2C module
 * @param[in] ifstart    when one send start signal, when zero don't
 * @retval
 *
 */
void tls_i2c_write_byte(u8 data, u8 ifstart)
{
    tls_reg_write32(HR_I2C_TX_RX, data);
    if (ifstart)
        tls_reg_write32(HR_I2C_CR_SR, I2C_CR_STA | I2C_CR_WR);
    else
        tls_reg_write32(HR_I2C_CR_SR, I2C_CR_WR);
    while (tls_reg_read32(HR_I2C_CR_SR) & I2C_SR_TIP);
}

/**
 * @brief    get the data stored in data register of I2C module
 * @param[in] ifack    when one send ack after reading the data register,when zero don't
 * @param[in] ifstop when one send stop signal after read, when zero do not send stop
 * @retval    the received data
 */
u8 tls_i2c_read_byte(u8 ifack, u8 ifstop)
{
    u8 data;
    u32 value = I2C_CR_RD;

    if (!ifack)
        value |= I2C_CR_NAK;
    if (ifstop)
        value |= I2C_CR_STO;

    tls_reg_write32(HR_I2C_CR_SR, value);
    /** Waiting finish */
    while (tls_reg_read32(HR_I2C_CR_SR) & I2C_SR_TIP);
    data = tls_reg_read32(HR_I2C_TX_RX);

    return data;
}

/**
 * @brief    start write through int mode
 * @param[in] devaddr    the device address
 * @param[in] wordaddr when one send stop signal after read, when zero do not send stop
 * @param[in] buf    the address point where data shoule be stored
 * @param[in] len    the length of data will be received
 * @retval
 *    - \ref WM_FAILED
 *    - \ref WM_SUCCESS
 */
int wm_i2c_start_write_it(uint8_t devaddr, uint8_t wordaddr, uint8_t * buf, uint16_t len)
{
    if (buf == NULL) {
        return WM_FAILED;
    }
    I2C->TX_RX = devaddr;
    i2c_transfer.dev_addr = devaddr;
    i2c_transfer.state = START;
    i2c_transfer.cmd = I2C_WRITE;
    i2c_transfer.buf = buf;
    i2c_transfer.len = len;
    i2c_transfer.cnt = 0;
    i2c_transfer.addr = wordaddr;
    I2C->CR_SR = I2C_CR_STA | I2C_CR_WR;
    return WM_SUCCESS;
}

/**
 * @brief    start read through int mode
 * @param[in] devaddr    the device address
 * @param[in] wordaddr when one send stop signal after read, when zero do not send stop
 * @param[in] buf    the address point where data shoule be stored
 * @param[in] len    the length of data will be received
 * @retval
 *    - \ref WM_FAILED
 *    - \ref WM_SUCCESS
 */
int wm_i2c_start_read_it(uint8_t devaddr, uint8_t wordaddr, uint8_t * buf, uint16_t len)
{
    if (buf == NULL) {
        return WM_FAILED;
    }
    I2C->TX_RX = devaddr;
    i2c_transfer.dev_addr = devaddr;
    i2c_transfer.state = START;
    i2c_transfer.cmd = I2C_READ;
    i2c_transfer.buf = buf;
    i2c_transfer.len = len;
    i2c_transfer.cnt = 0;
    i2c_transfer.addr = wordaddr;
    I2C->CR_SR = I2C_CR_STA | I2C_CR_WR;

    return WM_SUCCESS;
}

/**
 * @brief          This function is used to register i2c transfer done callback function.
 * @param[in]      done  is the i2c transfer done callback function.
 * @retval         None
 * @note           None
 */
void wm_i2c_transfer_done_register(void (*done)(void))
{
    i2c_transfer.transfer_done = done;
}

/*** (C) COPYRIGHT 2014 Winner Microelectronics Co., Ltd. ***/
